using System.Collections.Concurrent;
using System.DirectoryServices;
using HelpDeskTool.Entities;
using HelpDeskTool.Extensions;
using HelpDeskTool.Views;
using Microsoft.Extensions.Logging;

namespace HelpDeskTool.Services;

public class Ldap : ILdap
{
	private readonly ILogger logger;

	public Ldap(ILogger<Ldap> logger)
	{
		this.logger = logger;
	}

	public async Task<IReadOnlyCollection<IComputer>> GetComputersAsync(CancellationToken cancellationToken = default)
	{
		var res = new ConcurrentBag<IComputer>();
		var collection = GetAllComputers().ToArray();

		var parallelOptions = new ParallelOptions
		{
			MaxDegreeOfParallelism = Environment.ProcessorCount,
			CancellationToken = cancellationToken
		};

		await Parallel.ForEachAsync(collection, parallelOptions, async (s, token) =>
		{
			await Task.Run(() =>
			{
				try
				{
					using var entry = s.GetDirectoryEntry();
					var comp = GetComputerFromLdap(entry);

					if (comp != null)
						res.Add(new ComputerView(comp));
				}
				catch (Exception e)
				{
					logger.LogException(e);
				}
			}, token);
		});

		return res;
	}

	private IEnumerable<SearchResult> GetAllComputers()
	{
		using var searcher = SearchEntryFromAD(null, LdapObjectCategory.Computer);
		using var all = searcher.FindAll();

		foreach (SearchResult sr in all)
			yield return sr;
	}

	private Computer? GetComputerFromLdap(DirectoryEntry entry)
	{
		var computerName = entry.Properties["name"].Value?.ToString();
		if (string.IsNullOrWhiteSpace(computerName))
			throw new Exception("Computer name is null or empty!");

		if (IsDisabledAccount(entry))
			return null;

		var computerDesc = entry.Properties["description"].Value?.ToString();
		var phone = GetProperty(computerDesc, "telephoneNumber");

		var computer = new Computer
		{
			Name = computerName,
			Description = computerDesc,
			Phone = phone
		};

		return computer;
	}

	private bool IsDisabledAccount(DirectoryEntry entry)
	{
		var userAccountControl = Convert.ToInt32(entry.Properties["userAccountControl"][0]);
		var disabled = (userAccountControl & 2) > 0;

		return disabled;
	}

	private DirectorySearcher SearchEntryFromAD(string? name, LdapObjectCategory filter)
	{
		using var connection = new DirectoryEntry();
		using var searcher = new DirectorySearcher(connection);

		searcher.Filter = name == null
			? $"(ObjectCategory={filter})"
			: $"(&(ObjectCategory={filter})(cn={name}))";

		return searcher;
	}

	private string? GetProperty(string? username, string property)
	{
		if (string.IsNullOrWhiteSpace(username))
			return null;

		using var searcher = SearchEntryFromAD(username, LdapObjectCategory.Person);
		using var entry = searcher.FindOne()?.GetDirectoryEntry();

		var prop = entry?.Properties[property];

		if (prop?.Capacity > 0)
			return prop[0]?.ToString();

		return null;
	}
}
